using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using Faml.Frontend;

namespace Faml.Sources.XDR
{
    public class XDRVariable
    {
        private string name;
        private int[] dim;
        private Type type;

        internal XDRVariable(string name, Type type, int dim) :
          this(name, type, new int[1] { dim })
            { }

        internal XDRVariable(string name, Type type, int dim1, int dim2) :
          this(name, type, new int[2] { dim1, dim2 })
            { }

        internal XDRVariable(string name, Type type, int[] dim)
        {
            this.name = name;
            this.type = type;
            this.dim = dim;
        }

        /// <summary>Returns name of variable</summary>
        public string Name { get { return name; } }

        /// <summary>Returns dimension of variable</summary>
        public int Dimensions { get { return dim.Length; } }

        public bool IsTwoDimensional
        {
            get
            {
                return (Dimensions == 2);
            }
        }
        /// <summary>Returns total array length for this variable</summary>
        public int Length
        {
            get
            {
                int length = 1;
                for (int i = 0; i < dim.Length; i++)
                    length *= dim[i];
                return length;
            }
        }

        /// <summary>Returns length of specified dimension</summary>
        /// <param name="index">Dimension index</param>
        /// <returns>Length of specified dimension</returns>
        public int GetLength(int index) { return dim[index]; }

        public Type Type { get { return type; } }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(type.Name);
            sb.Append(' ');
            sb.Append(name);
            for (int i = 0; i < Dimensions; i++)
            {
                sb.Append('[');
                sb.Append(dim[i]);
                sb.Append(']');
            }
            return sb.ToString();
        }
    }

    public class XDRHeader
    {
        protected List<XDRVariable> variables;
        protected long dataOffset;

        public XDRHeader(Stream stream)
        {
            Parser p = new Parser(stream);
            variables = ParseDDS(p);
            if (!SeekLabel(stream, Encoding.ASCII.GetBytes("Data:\n")))
                throw new Exception("No data signature found");
            //dataOffset = stream.Position;
        }

        public long DataOffset
        {
            get
            {
                return dataOffset;
            }
        }

        public List<XDRVariable> Variables
        {
            get
            {
                return variables; 
            }
        }

        public static bool SeekLabel(Stream s, byte[] sequence)
        {
            int idx = -1;
            while (true)
            {
                int ib = s.ReadByte();
                if (ib == -1)
                    return false;
                byte bt = (byte)ib;
                if (sequence[idx + 1] == bt)
                {
                    idx++;
                    if (idx >= sequence.Length - 1)
                        return true;
                }
                else
                    idx = -1;
            }
        }

        public static List<XDRVariable> ParseDDS(Parser parser)
        {
            List<XDRVariable> result = new List<XDRVariable>();
            if (parser.MoveNext() != TokenType.Ident || parser.TokenText != "Dataset")
                throw new Exception("Wrong DDS declaration - no Dataset token");
            if (parser.MoveNext() != TokenType.Delimiter || parser.TokenText != "{")
                throw new Exception("Wrong DDS declaration - syntax error!");
            while (parser.MoveNext() == TokenType.Ident)
            {
                Type type = GetTypeFromString(parser.TokenText);
                if (parser.MoveNext() != TokenType.Ident)
                    throw new Exception("Wrong DDS declaration - name expected");
                string name = parser.TokenText;
                List<int> dimensions = new List<int>();
                while (parser.MoveNext() == TokenType.Delimiter && parser.TokenText == "[")
                {
                    if (parser.MoveNext() != TokenType.Ident)
                        throw new Exception("Wrong DDS declaration - no dimension name");
                    if (parser.MoveNext() != TokenType.Delimiter || parser.TokenText != "=")
                        throw new Exception("Wrong DDS declaration - no equal sign");
                    if (parser.MoveNext() != TokenType.Integer)
                        throw new Exception("Wrong DDS declaration - no dimension size");
                    dimensions.Add(parser.IntegerValue);
                    if (parser.MoveNext() != TokenType.Delimiter || parser.TokenText != "]")
                        throw new Exception("Wrong DDS declaration - no closing bracket");
                }
                if (parser.TokenType != TokenType.Delimiter || parser.TokenText != ";")
                    throw new Exception("Wrong DDS declaration - syntax error!");
                if (dimensions.Count == 0)
                    throw new Exception("Wrong DDS declaration - scalar values are not supported");
                result.Add(new XDRVariable(name, type, dimensions.ToArray()));
            }
            if (parser.TokenType != TokenType.Delimiter || parser.TokenText != "}")
                throw new Exception("Wrong DDS declaration - syntax error!");
            return result;
        }

        public static Type GetTypeFromString(string text)
        {
            switch (text)
            {
                case "Int32":
                    return typeof(int);
                case "Float32":
                    return typeof(float);
                case "Float64":
                    return typeof(double);
                case "Byte":
                    return typeof(byte);
                default:
                    throw new Exception("Wrong DDS declaration - unknown type " + text);
            }
        }
    }
}